package com.hibase.core.sequence.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageInfo;
import com.hibase.common.constant.HibaseBaseStatus;
import com.hibase.common.constant.RedisKeyConstant;
import com.hibase.common.exception.HibaseAssert;
import com.hibase.core.lock.annotation.HibaseLock;
import com.hibase.core.mybatis.service.BaseServiceImpl;
import com.hibase.core.redis.util.RedisUtil;
import com.hibase.core.sequence.constant.CodeSequenceScale;
import com.hibase.core.sequence.entity.MdsCodeSequence;
import com.hibase.core.sequence.entity.MdsCodeSequenceRegex;
import com.hibase.core.sequence.entity.vo.MdsCodeSequenceRefreshVO;
import com.hibase.core.sequence.entity.vo.MdsCodeSequenceRegexSearchVO;
import com.hibase.core.sequence.mapper.MdsCodeSequenceRegexMapper;
import com.hibase.core.sequence.service.MdsCodeSequenceRegexService;
import com.hibase.core.sequence.service.MdsCodeSequenceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * <p>
 * 流水号通配符规则表 服务实现类
 * </p>
 *
 * @author chenfeng
 * @since 2021-01-19
 */
@Slf4j
@Service
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class MdsCodeSequenceRegexServiceImpl extends BaseServiceImpl<MdsCodeSequenceRegexMapper, MdsCodeSequenceRegex> implements MdsCodeSequenceRegexService {

    @Autowired
    private MdsCodeSequenceService mdsCodeSequenceService;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 查询流水号通配符规则表列表
     */
    @Override
    public PageInfo<MdsCodeSequenceRegex> selectMdsCodeSequenceRegexList(MdsCodeSequenceRegexSearchVO searchVO) {
        QueryWrapper query = new QueryWrapper<MdsCodeSequenceRegex>();
        PageInfo<MdsCodeSequenceRegex> page = super.page(searchVO, query);
        if (CollUtil.isEmpty(page.getList())) return page;
        List<String> ids = page.getList().stream().map(MdsCodeSequenceRegex::getSequenceId).collect(Collectors.toList());
        Map<String, MdsCodeSequence> sequenceMap = mdsCodeSequenceService.listByIds(ids).stream().collect(Collectors.toMap(MdsCodeSequence::getId, Function.identity(), (o1, o2) -> o1));
        page.getList().forEach(regex -> {
            String sequenceId = regex.getSequenceId();
            MdsCodeSequence sequence = sequenceMap.get(sequenceId);
            regex.setSequenceType(sequence.getSequenceType());
            regex.setCodeLength(sequence.getCodeLength());
            regex.setFormatStr(sequence.getFormatStr());
            regex.setIncrementStep(sequence.getIncrementStep());
            regex.setMaxValue(sequence.getMaxValue());
            regex.setScale(sequence.getScale());
        });
        return page;
    }

    /**
     * 改变流水号通配符规则表记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveMdsCodeSequenceRegex(MdsCodeSequenceRegex entity) {

        super.store(entity);
    }

    /**
     * 查询流水号通配符规则表详情
     */
    @Override
    public MdsCodeSequenceRegex viewMdsCodeSequenceRegex(String id) {

        MdsCodeSequenceRegex entity = super.getById(id, true);

        return entity;
    }

    /**
     * 删除流水号通配符规则表记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMdsCodeSequenceRegex(String id) {

        super.deleteBy(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    @HibaseLock
    public String generateCode(String sequenceType, String fix) {
        return this.codeFormat(sequenceType, fix);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public String generateCode(String sequenceType) {
        return this.generateCode(sequenceType, null);
    }

    /**
     * 调整步长
     *
     * @param refreshVO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refreshMdsCodeSequence(MdsCodeSequenceRefreshVO refreshVO) {
        BigDecimal curStep = refreshVO.getCurStep();
        HibaseAssert.isTrue(curStep.intValue() != 0, "输入的值不能为0");
        MdsCodeSequenceRegex sequenceRegex = super.getById(refreshVO.getId());
        HibaseAssert.notNull(sequenceRegex, "service.mds.sequence.impl.mdsCodeSequenceServiceImpl.orderSequenceRuleNotFound");
        MdsCodeSequence sequence = mdsCodeSequenceService.getById(sequenceRegex.getSequenceId());
        HibaseAssert.isTrue(curStep.intValue() % sequence.getIncrementStep() == 0, "输入的值必须是incrementStep的倍数");
        String dymTime = DateUtil.format(DateUtil.date(), sequence.getFormatStr());
        // 缓存中当前值
        String step = this.getCurStep(sequence.getSequenceType(), sequenceRegex.getFix(), dymTime);
        if (StrUtil.isNotBlank(step)) {
            HibaseAssert.isTrue(curStep.intValue() >= Integer.parseInt(step), "输入的值不能小于当前值");
        }
        // 刷新当前值
        sequenceRegex.setCurStep(curStep.toString());
        super.store(sequenceRegex);

        String fix = StrUtil.EMPTY;
        if (sequence.getBeRegex()) {
            //得到前缀
            String replace = this.getFix(sequence.getFix(), fix);
            //除去前缀
            fix = StrUtil.removePrefix(sequenceRegex.getFix(), replace);
        }
        // 清理redis的值
        String key = this.getSequenceTypeKey(sequence.getSequenceType(), fix, dymTime);
        redisUtil.delete(key);
    }

    private String codeFormat(String sequenceType, String fix) {
        // 获取订单编号样式
        String codeSequenceNo = this.getCodeSequenceNo(sequenceType, fix);
        return codeSequenceNo;
    }

    private String getCodeSequenceNo(String sequenceType, String fix) {
        // step 01 查询数据库得到业务订单配置记录
        // 查询数据库得到业务订单配置记录
        QueryWrapper<MdsCodeSequence> queryWrapper = new QueryWrapper<>();

        queryWrapper.eq("sequence_type", sequenceType);

        MdsCodeSequence codeSequence = mdsCodeSequenceService.getOne(queryWrapper);

        HibaseAssert.notNull(codeSequence, "获取订单编号样式不存在");
        //生成前缀
        String prefix = StrUtil.EMPTY;
        if (codeSequence.getBeRegex()) {
            HibaseAssert.notNull(fix, "当前流水号必须要用通配符");
            prefix = this.getFix(codeSequence.getFix(), fix);
        } else {
            fix = StrUtil.EMPTY;
            prefix = codeSequence.getFix();
        }

        Date now = new Date();

        String dymTime = StrUtil.EMPTY;

        if (StrUtil.isBlank(codeSequence.getFormatStr())) {

            dymTime = DateUtil.format(now, "yyMMdd");
        } else {

            dymTime = DateUtil.format(now, codeSequence.getFormatStr());
        }

        // step 02 查询缓存队列中是否有生成好的订单号
        String code = this.getCodeByQueue(sequenceType, fix, dymTime);
        // step 03 不存在
        if (StrUtil.isBlank(code)) {
            //从缓存中读取序号当前值
            MdsCodeSequenceRegex sequenceRegex = super.getOne(new QueryWrapper<MdsCodeSequenceRegex>().eq("fix", prefix)
                    .eq("sequence_id", codeSequence.getId()));
            String curStep = this.getCurStep(sequenceType, fix, dymTime);
            // 缓存中没有则赋值为初始值0
            if (ObjectUtil.isEmpty(sequenceRegex)) {
                sequenceRegex = new MdsCodeSequenceRegex();
                sequenceRegex.setCurStep(BigDecimal.ZERO.toString());
                sequenceRegex.setSequenceId(codeSequence.getId());
                sequenceRegex.setFix(prefix);
                sequenceRegex.setStatus(HibaseBaseStatus.ENABLE.getKey());
                sequenceRegex = super.store(sequenceRegex);
            }
            // 缓存中没有则赋值为初始值0
            if (StrUtil.isBlank(curStep)) {
                curStep = "0";
            } else {
                curStep = sequenceRegex.getCurStep();
            }

            // 生成n（步长）个订单号放置到redis队列中
            List<String> codeSequenceNos = this.genCodeSequenceNos(codeSequence, prefix, dymTime, curStep);
            this.saveCodeSequence(codeSequenceNos, sequenceType, fix, dymTime);
            // 更新数据库的当前值
            String step = new BigDecimal(curStep).add(new BigDecimal(codeSequence.getIncrementStep())).toString();
            sequenceRegex.setCurStep(step);
            super.store(sequenceRegex);
            // 保存序号当前值
            this.saveCurStep(sequenceType, fix, dymTime, curStep);
            // 从队列中取出一个
            // 从队列中取出一个
            code = this.getCodeByQueue(sequenceType, fix, dymTime);
            HibaseAssert.notNull(code, "单据号规则未配置");
        }
        return code;
    }

    private String getFix(String cur, String fix) {
        //字符串匹配
        Pattern r = Pattern.compile("#[{].*[}]");
        Matcher matcher = r.matcher(cur);
        HibaseAssert.isTrue(matcher.find(), "请检查流水号前缀通配符,必须为X#{XX}格式");
        return cur.replace(matcher.group(), fix);
    }

    /**
     * 从队列中获取订单编号
     */
    private String getCodeByQueue(String sequenceType, String fix, String time) {
        String key = getSequenceTypeKey(sequenceType, fix, time);
        if (redisUtil.hasKey(key)) {
            String codeSequence = (String) redisUtil.lRightPop(key);
            if (StrUtil.isNotBlank(codeSequence)) {
                return codeSequence;
            }
        }
        return null;
    }

    private String getSequenceTypeKey(String sequenceType, String fix, String time) {

        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append(RedisKeyConstant.CODE_SEQUENCE_KEY);
        stringBuilder.append(StrUtil.COLON);
        stringBuilder.append(sequenceType);
        if (StrUtil.isNotBlank(fix)) {
            stringBuilder.append(StrUtil.COLON);
            stringBuilder.append(fix);

        }
        if (StrUtil.isNotBlank(time)) {
            stringBuilder.append(StrUtil.COLON);
            stringBuilder.append(time);
        }

        return stringBuilder.toString();
    }

    /**
     * 获取序号当前值
     */
    public String getCurStep(String sequenceType, String fix, String time) {
        String key = getCurStepKey();
        String hashKey = sequenceType + "-" + fix + "-" + time;
        if (redisUtil.hasKey(key)) {
            String curStep = (String) redisUtil.hGet(key, hashKey);
            if (StrUtil.isNotBlank(curStep)) {
                return curStep;
            }
        }

        return null;
    }

    /**
     * 生成序号当前值的key
     */
    private String getCurStepKey() {

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(RedisKeyConstant.CODE_SEQUENCE_KEY);
        stringBuilder.append(StrUtil.COLON);
        stringBuilder.append(RedisKeyConstant.SEQUENCE_CUR_STEP_KEY);
        return stringBuilder.toString();
    }


    /**
     * 生成n（步长）个订单号放置到redis队列中
     */
    private List<String> genCodeSequenceNos(MdsCodeSequence codeSequence, String fix, String time, String curStep) {

        List<String> result = new ArrayList<>(codeSequence.getIncrementStep());

        int incrementStep = codeSequence.getIncrementStep();

        while (incrementStep-- > 0) {

            curStep = this.sequence(curStep, codeSequence.getScale(), codeSequence.getCodeLength());

            StringBuilder builder = new StringBuilder();

            builder.append(fix);
            builder.append(time);
            builder.append(curStep);
            result.add(builder.toString());
        }

        return result;
    }

    private String sequence(String sequenceValue, String scale, Integer length) {

        if (CodeSequenceScale.SEQUENCE_SCALE_TEN.equals(scale)) {
            sequenceValue = this.sequenceByDecimal(sequenceValue, length);
        } else if (CodeSequenceScale.SEQUENCE_SCALE_SIXTEEN.equals(scale)) {
            sequenceValue = this.sequenceByHexaDecimal(sequenceValue, length);
        } else if (CodeSequenceScale.SEQUENCE_SCALE_THIRTYSIX.equals(scale)) {
            sequenceValue = this.sequenceByThirtySixDecimal(sequenceValue, length);
        }
        return sequenceValue;
    }

    /**
     * 获取十进制流水号
     *
     * @param sequenceValue 当前值
     * @param length        流水号长度
     * @return
     */
    private String sequenceByDecimal(String sequenceValue, Integer length) {

        Integer sequence = Integer.valueOf(sequenceValue);
        String tempSequenceValue = String.valueOf(sequence + 1);
        return StrUtil.padPre(tempSequenceValue, length, RedisKeyConstant.SEQUENCE_REPLACE_STR);
    }

    /**
     * 获取十六进制流水号
     *
     * @param sequenceValue 当前值
     * @param length        流水号长度
     * @return
     */
    private String sequenceByHexaDecimal(String sequenceValue, Integer length) {
        String scaleStr = "0123456789ABCDEF";
        return this.scaleCalculate(sequenceValue, scaleStr, length);
    }

    /**
     * 获取三十六进制流水号
     *
     * @param sequenceValue 当前值
     * @param length        流水号长度
     * @return
     */
    private String sequenceByThirtySixDecimal(String sequenceValue, Integer length) {
        String scaleStr = "0123456789ABCDEFGHIGKLMNOPQRSTUVWXYZ";
        return this.scaleCalculate(sequenceValue, scaleStr, length);
    }

    /**
     * 根据进制换算值 +1
     *
     * @param sequenceValue 当前值
     * @param scaleStr      进制字符串
     * @param length        流水号长度
     * @return
     */
    private String scaleCalculate(String sequenceValue, String scaleStr, Integer length) {
        char toZero = scaleStr.charAt(0);
        String gotoNumberStr = "";
        //原流水号
        char[] numberChars = sequenceValue.toCharArray();
        //是否进一位
        Boolean beGoto = Boolean.FALSE;
        //原流水号末一位在进制字符串的位置
        int index = scaleStr.indexOf(numberChars[numberChars.length - 1]) + 1;
        //如果原流水号的末一位在进制字符串中的位置是最后一位，则表示需要进一位
        if (index == scaleStr.length()) {
            gotoNumberStr = toZero + gotoNumberStr;
            beGoto = Boolean.TRUE;
        } else {
            gotoNumberStr = scaleStr.substring(index, index + 1).charAt(0) + gotoNumberStr;
        }
        //如果最后一位需要进一位，则从倒数第二位开始，逐一判断是否需要进一位，并将已经进一位的结果放到加后的数组
        for (int i = numberChars.length - 2; i >= 0; i--) {
            if (beGoto) {
                index = scaleStr.indexOf(numberChars[i]) + 1;
                if (index == scaleStr.length()) {
                    gotoNumberStr = toZero + gotoNumberStr;
                    beGoto = Boolean.TRUE;
                } else {
                    gotoNumberStr = scaleStr.substring(index, index + 1).charAt(0) + gotoNumberStr;
                    beGoto = Boolean.FALSE;
                }
            } else {
                gotoNumberStr = numberChars[i] + gotoNumberStr;
                beGoto = Boolean.FALSE;
            }
        }
        if (beGoto) {
            gotoNumberStr = toZero + gotoNumberStr;
        }

        return gotoNumberStr.toString();
    }

    /**
     * 保存订单编号样式缓存
     */
    public void saveCodeSequence(List<String> codeSequences, String sequenceType, String fix, String time) {
        if (CollUtil.isEmpty(codeSequences)) {
            log.info("对象不合法，生成的订单序列为空");
            return;
        }
        String key = this.getSequenceTypeKey(sequenceType, fix, time);

        redisUtil.lLeftPushAll(key,codeSequences.toArray(new String[codeSequences.size()]));
        // 设置过期时间为1天
        redisUtil.expire(key, RedisKeyConstant.CODE_SEQUENCE_EXPIRE, TimeUnit.SECONDS);
    }

    /**
     * 保存序号当前值
     */
    public void saveCurStep(String sequenceType, String fix, String time, String curStep) {
        String key = getCurStepKey();
        String hashKey = sequenceType + "-" + fix + "-" + time;
        redisUtil.hPut(key, hashKey, curStep);
        if (StrUtil.isNotBlank(time)) {
            redisUtil.expire(key, RedisKeyConstant.CODE_SEQUENCE_EXPIRE, TimeUnit.SECONDS);
        }
    }


}
